<!DOCTYPE html>
<html>
<head>
    <title>完整ECDH加密通信客户端</title>
    <meta charset="UTF-8">
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
        .container { max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
        .section { margin-bottom: 25px; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background: #fff; }
        h1 { color: #2c3e50; text-align: center; }
        h2 { color: #3498db; border-bottom: 2px solid #eee; padding-bottom: 10px; }
        button { margin: 8px; padding: 10px 18px; background: #3498db; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; transition: background 0.3s; }
        button:hover { background: #2980b9; }
        button:disabled { background: #95a5a6; cursor: not-allowed; }
        input[type="text"], textarea { width: 100%; padding: 12px; margin: 8px 0; box-sizing: border-box; border: 1px solid #ddd; border-radius: 5px; font-size: 14px; }
        textarea { height: 100px; resize: vertical; font-family: monospace; }
        .result { background: #f9f9f9; padding: 15px; border-radius: 5px; margin-top: 12px; border-left: 4px solid #3498db; word-break: break-all; }
        .status { padding: 10px; margin: 10px 0; border-radius: 5px; background: #f8f9fa; border-left: 4px solid #6c757d; }
        .success { background: #d4edda; border-left-color: #28a745; color: #155724; }
        .error { background: #f8d7da; border-left-color: #dc3545; color: #721c24; }
        .warning { background: #fff3cd; border-left-color: #ffc107; color: #856404; }
        .key-info { display: flex; gap: 15px; margin: 10px 0; }
        .key-info div { flex: 1; }
        label { font-weight: bold; display: block; margin: 10px 0 5px; color: #2c3e50; }
        .action-buttons { display: flex; flex-wrap: wrap; gap: 10px; margin: 15px 0; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔐 完整ECDH加密通信客户端</h1>
        
        <div class="section">
            <h2>1. 客户端密钥对生成</h2>
            <div class="action-buttons">
                <button onclick="generateKeyPair()">生成客户端密钥对</button>
                <button onclick="copyToClipboard('clientPublicKey')">复制公钥</button>
                <button onclick="copyToClipboard('clientPrivateKey')">复制私钥</button>
            </div>
            <div class="key-info">
                <div>
                    <label>公钥 (Base64):</label>
                    <div class="result" id="clientPublicKey">尚未生成</div>
                </div>
                <div>
                    <label>私钥 (Base64):</label>
                    <div class="result" id="clientPrivateKey">尚未生成</div>
                </div>
            </div>
        </div>
        
        <div class="section">
            <h2>2. 服务端密钥配置</h2>
            <label>服务端公钥 (Base64, 从Java服务端获取):</label>
            <textarea id="serverPublicKey" placeholder="在此粘贴Java服务端生成的Base64公钥..."></textarea>
            
            <label>服务端私钥 (Base64, 可选, 仅用于测试):</label>
            <input type="text" id="serverPrivateKey" placeholder="输入服务端私钥 (可选)">
            
            <div class="action-buttons">
                <button onclick="testServerPublicKey()">测试服务端公钥格式</button>
                <button onclick="clearServerKeys()">清除服务端密钥</button>
            </div>
            <div class="result" id="serverKeyTestResult">尚未测试</div>
        </div>
        
        <div class="section">
            <h2>3. 共享密钥计算</h2>
            <div class="action-buttons">
                <button onclick="calculateSharedSecret()">计算共享密钥</button>
                <button onclick="copyToClipboard('sharedSecret')">复制共享密钥</button>
            </div>
            <div class="result" id="sharedSecret">尚未计算</div>
        </div>
        
        <div class="section">
            <h2>4. 消息加密</h2>
            <label>要加密的消息:</label>
            <textarea id="messageToEncrypt" placeholder="输入要加密的消息..."></textarea>
            
            <div class="action-buttons">
                <button onclick="encryptMessage()">加密消息</button>
                <button onclick="copyToClipboard('encryptedMessage')">复制加密结果</button>
            </div>
            <div class="result" id="encryptedMessage">尚未加密</div>
        </div>
        
        <div class="section">
            <h2>5. 消息解密</h2>
            <label>要解密的密文 (Base64):</label>
            <textarea id="messageToDecrypt" placeholder="输入要解密的Base64密文..."></textarea>
            
            <div class="action-buttons">
                <button onclick="decryptMessage()">解密消息</button>
                <button onclick="copyToClipboard('decryptedMessage')">复制解密结果</button>
            </div>
            <div class="result" id="decryptedMessage">尚未解密</div>
        </div>
        
        <div class="section">
            <h2>6. 操作状态</h2>
            <div class="status" id="status">页面已加载，请先生成客户端密钥对</div>
        </div>
    </div>

    <script>
        // 全局变量
        let clientKeyPair = null;
        let sharedSecret = null;
        let derivedAesKey = null;
        
        // 更新状态信息
        function updateStatus(message, type = 'info') {
            const statusElement = document.getElementById('status');
            statusElement.textContent = message;
            statusElement.className = 'status';
            
            if (type === 'success') statusElement.classList.add('success');
            else if (type === 'error') statusElement.classList.add('error');
            else if (type === 'warning') statusElement.classList.add('warning');
            
            console.log(`${type}: ${message}`);
        }
        
        // 复制到剪贴板
        function copyToClipboard(elementId) {
            const element = document.getElementById(elementId);
            const text = element.textContent || element.value;
            
            if (text === '尚未生成' || text === '尚未计算' || text === '尚未加密' || text === '尚未解密') {
                updateStatus('没有内容可复制', 'warning');
                return;
            }
            
            navigator.clipboard.writeText(text).then(() => {
                updateStatus(`已复制到剪贴板: ${elementId}`, 'success');
            }).catch(err => {
                updateStatus('复制失败: ' + err.message, 'error');
            });
        }
        
        // 清除服务端密钥
        function clearServerKeys() {
            document.getElementById('serverPublicKey').value = '';
            document.getElementById('serverPrivateKey').value = '';
            document.getElementById('serverKeyTestResult').textContent = '已清除';
            updateStatus('服务端密钥已清除');
        }
        
        // 生成密钥对
        async function generateKeyPair() {
            try {
                updateStatus('正在生成客户端ECDH密钥对...');
                
                clientKeyPair = await window.crypto.subtle.generateKey(
                    {
                        name: "ECDH",
                        namedCurve: "P-256"
                    },
                    true,
                    ["deriveKey", "deriveBits"]
                );
                
                // 导出公钥 (原始格式)
                const publicKeyBytes = await window.crypto.subtle.exportKey("raw", clientKeyPair.publicKey);
                const publicKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(publicKeyBytes)));
                document.getElementById('clientPublicKey').textContent = publicKeyBase64;
                
                // 导出私钥 (PKCS#8格式)
                const privateKeyBytes = await window.crypto.subtle.exportKey("pkcs8", clientKeyPair.privateKey);
                const privateKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(privateKeyBytes)));
                document.getElementById('clientPrivateKey').textContent = privateKeyBase64;
                
                updateStatus('客户端密钥对生成成功！', 'success');
                
            } catch (error) {
                updateStatus('生成密钥对失败: ' + error.message, 'error');
                console.error('生成密钥对失败:', error);
            }
        }
        
        // 测试服务端公钥格式
        async function testServerPublicKey() {
            const serverPublicKeyBase64 = document.getElementById('serverPublicKey').value.trim();
            if (!serverPublicKeyBase64) {
                updateStatus('请输入服务端公钥', 'warning');
                return;
            }
            
            try {
                updateStatus('正在测试服务端公钥格式...');
                
                const serverPublicKeyBytes = Uint8Array.from(atob(serverPublicKeyBase64), c => c.charCodeAt(0));
                
                // 尝试导入为原始格式
                try {
                    const serverPublicKey = await window.crypto.subtle.importKey(
                        "raw",
                        serverPublicKeyBytes,
                        { name: "ECDH", namedCurve: "P-256" },
                        false,
                        []
                    );
                    document.getElementById('serverKeyTestResult').textContent = 
                        '✅ 公钥格式正确 (原始格式)';
                    updateStatus('服务端公钥格式正确，可以用于计算共享密钥', 'success');
                } catch (e) {
                    document.getElementById('serverKeyTestResult').textContent = 
                        '❌ 原始格式导入失败: ' + e.message;
                    updateStatus('服务端公钥格式可能不兼容: ' + e.message, 'error');
                }
                
            } catch (error) {
                document.getElementById('serverKeyTestResult').textContent = 
                    '❌ 错误: ' + error.message;
                updateStatus('服务端公钥测试失败: ' + error.message, 'error');
            }
        }
        
        // 计算共享密钥
        async function calculateSharedSecret() {
            const serverPublicKeyBase64 = document.getElementById('serverPublicKey').value.trim();
            if (!serverPublicKeyBase64) {
                updateStatus('请输入服务端公钥', 'warning');
                return;
            }
            
            if (!clientKeyPair) {
                updateStatus('请先生成客户端密钥对', 'warning');
                return;
            }
            
            try {
                updateStatus('正在计算共享密钥...');
                
                const serverPublicKeyBytes = Uint8Array.from(atob(serverPublicKeyBase64), c => c.charCodeAt(0));
                
                // 导入服务端公钥
                const serverPublicKey = await window.crypto.subtle.importKey(
                    "raw",
                    serverPublicKeyBytes,
                    { name: "ECDH", namedCurve: "P-256" },
                    false,
                    []
                );
                
                // 计算共享密钥
                sharedSecret = await window.crypto.subtle.deriveBits(
                    {
                        name: "ECDH",
                        public: serverPublicKey
                    },
                    clientKeyPair.privateKey,
                    256
                );
                
                // 派生AES密钥
                derivedAesKey = await window.crypto.subtle.importKey(
                    "raw",
                    sharedSecret,
                    { name: "AES-GCM" },
                    false,
                    ["encrypt", "decrypt"]
                );
                
                // 显示共享密钥
                const sharedSecretArray = new Uint8Array(sharedSecret);
                const sharedSecretBase64 = btoa(String.fromCharCode(...sharedSecretArray));
                document.getElementById('sharedSecret').textContent = sharedSecretBase64;
                
                updateStatus('共享密钥计算成功！现在可以加密和解密消息了', 'success');
                
            } catch (error) {
                updateStatus('计算共享密钥失败: ' + error.message, 'error');
                console.error('计算共享密钥失败:', error);
            }
        }
        
        // 加密消息
        async function encryptMessage() {
            const message = document.getElementById('messageToEncrypt').value.trim();
            if (!message) {
                updateStatus('请输入要加密的消息', 'warning');
                return;
            }
            
            if (!derivedAesKey) {
                updateStatus('请先计算共享密钥', 'warning');
                return;
            }
            
            try {
                updateStatus('正在加密消息...');
                
                // 生成随机IV
                const iv = window.crypto.getRandomValues(new Uint8Array(12));
                
                // 加密数据
                const encodedMessage = new TextEncoder().encode(message);
                const encryptedData = await window.crypto.subtle.encrypt(
                    {
                        name: "AES-GCM",
                        iv: iv
                    },
                    derivedAesKey,
                    encodedMessage
                );
                
                // 组合IV和加密数据
                const combined = new Uint8Array(iv.length + encryptedData.byteLength);
                combined.set(iv, 0);
                combined.set(new Uint8Array(encryptedData), iv.length);
                
                // 显示加密结果
                const encryptedBase64 = btoa(String.fromCharCode(...combined));
                document.getElementById('encryptedMessage').textContent = encryptedBase64;
                
                updateStatus('消息加密成功！', 'success');
                
            } catch (error) {
                updateStatus('加密失败: ' + error.message, 'error');
                console.error('加密失败:', error);
            }
        }
        
        // 解密消息
        async function decryptMessage() {
            const encryptedMessage = document.getElementById('messageToDecrypt').value.trim();
            if (!encryptedMessage) {
                updateStatus('请输入要解密的密文', 'warning');
                return;
            }
            
            if (!derivedAesKey) {
                updateStatus('请先计算共享密钥', 'warning');
                return;
            }
            
            try {
                updateStatus('正在解密消息...');
                
                // 解析加密数据
                const encryptedBytes = Uint8Array.from(atob(encryptedMessage), c => c.charCodeAt(0));
                
                if (encryptedBytes.length < 12) {
                    throw new Error('加密数据太短，缺少IV');
                }
                
                const iv = encryptedBytes.slice(0, 12);
                const data = encryptedBytes.slice(12);
                
                // 解密数据
                const decryptedData = await window.crypto.subtle.decrypt(
                    {
                        name: "AES-GCM",
                        iv: iv
                    },
                    derivedAesKey,
                    data
                );
                
                // 显示解密结果
                const decryptedMessage = new TextDecoder().decode(decryptedData);
                document.getElementById('decryptedMessage').textContent = decryptedMessage;
                
                updateStatus('消息解密成功！', 'success');
                
            } catch (error) {
                updateStatus('解密失败: ' + error.message, 'error');
                console.error('解密失败:', error);
            }
        }
        
        // 初始化
        updateStatus('页面已加载完成。请按照顺序执行操作：1. 生成密钥对 → 2. 输入服务端公钥 → 3. 计算共享密钥 → 4. 加密/解密消息');
    </script>
</body>
</html>